#!/usr/bin/env python 
'''
Copyright (C) 2005, 2008 Aaron Spike, aaron@ekips.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'''
import struct, sys
import os.path, cStringIO

if sys.platform == "win32":
    import os, msvcrt
    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
    msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)

from lxml import etree as et


"""
Drawing Object Types:
0   line
1   polyline
4   elliptical arc
5   nurb spline
6   text
7   rectangle
9   rounded rectangle
"""
GSD_DICT = {
'03':{
    '01':'Number of Drawing Objects'
    },
'04':{
    '00':'Drawing Object',
    '01':'Drawing Object Type',
    '0A':'2D Point',
    '0B':'Polyline Length',
    '0C':'Polyline Point',
    '14':'Sweep (radians)',
    '15':'Ecentricity?',
    '16':'(0=Open/1=Closed)',
    '40':'Bbox (Upper Left)',
    '41':'Bbox (Upper Right)',
    '42':'Bbox (Lower Right)',
    '43':'Bbox (Lower Left)',
    '44':'Rotation'
    },
'05':{
    '21':'Group'
    },
'07':{
    '02':'Number of Line Styles?'
    },
'0A':{
    '00':'Page Style?'
    },
'0B':{
    '00':'Line Style?'
    },
'0E':{
    '00':'Registration Mark Settings',
    '01':'Use Marks',
    '0A':'Size',
    '0B':'Width',
    '0C':'Origin X',
    '0D':'Origin Y',
    '0E':'Distance (1-2)',
    '0F':'Distance (2-3)'
    },
'0F':{
    '00':'Grid Settings',
    '01':'Show',
    '02':'Type',
    '03':'Spacing',
    '04':'Snap'
    }
}
GSD_DOCROOT = '<gsd />'
GSD_MINIMAL = '''<gsd>
  <header>
    <magic>GRAPHTEC PRT&amp;CUT</magic>
    <unknown>3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</unknown>
  </header>
  <body>
    <section index='0' group='1'>
      <chunk count='1' index='0' group='1' type='0'/>
      <chunk count='1' index='1' group='1' type='3'>
        <datum>1</datum>
      </chunk>
    </section>
    <section index='0' group='2'>
      <chunk count='4' index='0' group='2' type='0'/>
      <chunk count='1' index='1' group='2' type='5'>
        <datum>0</datum>
      </chunk>
      <chunk count='1' index='2' group='2' type='3'>
        <datum>7</datum>
      </chunk>
      <chunk count='1' index='3' group='2' type='3'>
        <datum>0</datum>
      </chunk>
      <chunk count='1' index='4' group='2' type='1'>
        <datum>0</datum>
      </chunk>
    </section>
    <section index='0' group='3'>
      <chunk count='5' index='0' group='3' type='0'/>
      <chunk count='1' index='1' group='3' type='3'>
        <datum>0</datum>
      </chunk>
      <chunk count='2' index='2' group='3' type='4'>
        <datum>0.0</datum>
        <datum>0.0</datum>
      </chunk>
      <chunk count='2' index='3' group='3' type='4'>
        <datum>210.0</datum>
        <datum>297.0</datum>
      </chunk>
      <chunk count='2' index='4' group='3' type='4'>
        <datum>0.0</datum>
        <datum>0.0</datum>
      </chunk>
      <chunk count='2' index='5' group='3' type='4'>
        <datum>210.0</datum>
        <datum>297.0</datum>
      </chunk>
    </section>
    <drawing>
    </drawing>
    <section index='0' group='6'>
      <chunk count='6' index='0' group='6' type='0'/>
      <section index='1' group='6'>
        <chunk count='0' index='1' group='6' type='0'/>
        <section index='0' group='7'>
          <chunk count='6' index='0' group='7' type='0'/>
          <chunk count='1' index='1' group='7' type='1'>
            <datum>2</datum>
          </chunk>
          <section index='2' group='7'>
            <chunk count='1' index='2' group='7' type='3'>
              <datum>1</datum>
            </chunk>
            <section index='0' group='11'>
              <chunk count='6' index='0' group='11' type='0'/>
              <chunk count='1' index='1' group='11' type='3'>
                <datum>0</datum>
              </chunk>
              <chunk count='18' index='2' group='11' type='5'>
                <datum>RGB(  0,   0,   0)</datum>
              </chunk>
              <chunk count='1' index='3' group='11' type='3'>
                <datum>0</datum>
              </chunk>
              <chunk count='1' index='4' group='11' type='1'>
                <datum>1</datum>
              </chunk>
              <chunk count='1' index='5' group='11' type='1'>
                <datum>1</datum>
                <datum>11</datum>
                <datum type='5'>Default.gcf</datum>
              </chunk>
              <chunk count='1' index='6' group='11' type='3'>
                <datum>0</datum>
              </chunk>
            </section>
          </section>
          <chunk count='1' index='3' group='7' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='0' index='4' group='7' type='5'>
            <datum>
            </datum>
          </chunk>
          <chunk count='1' index='5' group='7' type='4'>
            <datum>2.0</datum>
          </chunk>
          <chunk count='1' index='6' group='7' type='4'>
            <datum>4.0</datum>
          </chunk>
        </section>
      </section>
      <section index='2' group='6'>
        <chunk count='0' index='2' group='6' type='0'/>
        <section index='0' group='10'>
          <chunk count='28' index='0' group='10' type='0'/>
          <chunk count='0' index='1' group='8' type='5'>
            <datum>
            </datum>
          </chunk>
          <chunk count='0' index='2' group='8' type='5'>
            <datum>
            </datum>
          </chunk>
          <chunk count='11' index='4' group='8' type='5'>
            <datum>User Size 1</datum>
          </chunk>
          <chunk count='2' index='5' group='8' type='3'>
            <datum>21000</datum>
            <datum>29700</datum>
          </chunk>
          <chunk count='1' index='27' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='26' group='8' type='2'>
            <datum>100</datum>
          </chunk>
          <chunk count='1' index='9' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='10' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='11' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='12' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='13' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='34' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='32' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='33' group='8' type='2'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='35' group='8' type='2'>
            <datum>9</datum>
          </chunk>
          <chunk count='1' index='36' group='8' type='3'>
            <datum>2100</datum>
          </chunk>
          <chunk count='1' index='37' group='8' type='3'>
            <datum>2970</datum>
          </chunk>
          <chunk count='1' index='38' group='8' type='2'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='8' group='8' type='2'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='14' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='15' group='8' type='1'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='16' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='30' group='8' type='1'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='31' group='8' type='1'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='17' group='8' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='28' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='29' group='8' type='3'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='20' group='8' type='3'>
            <datum>0</datum>
          </chunk>
        </section>
      </section>
      <section index='3' group='6'>
        <chunk count='0' index='3' group='6' type='0'/>
        <section index='0' group='9'>
          <chunk count='4' index='0' group='9' type='0'/>
          <chunk count='1' index='1' group='9' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='2' group='9' type='1'>
            <datum>5</datum>
          </chunk>
          <chunk count='1' index='3' group='9' type='1'>
            <datum>5</datum>
          </chunk>
          <chunk count='1' index='4' group='9' type='1'>
            <datum>0</datum>
          </chunk>
        </section>
      </section>
      <section index='4' group='6'>
        <chunk count='0' index='4' group='6' type='0'/>
        <section index='0' group='10'>
          <chunk count='2' index='0' group='10' type='0'/>
          <chunk count='1' index='1' group='10' type='3'>
            <datum>0</datum>
          </chunk>
          <section index='2' group='10'>
            <chunk count='0' index='2' group='10' type='0'/>
            <section index='0' group='12'>
              <chunk count='1' index='0' group='12' type='0'/>
              <chunk count='1' index='1' group='12' type='3'>
                <datum>0</datum>
              </chunk>
            </section>
          </section>
        </section>
      </section>
      <section index='5' group='6'>
        <chunk count='0' index='5' group='6' type='0'/>
        <section index='0' group='14'>
          <chunk count='9' index='0' group='14' type='0'/>
          <chunk count='1' index='1' group='14' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='2' group='14' type='2'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='3' group='14' type='2'>
            <datum>1</datum>
          </chunk>
          <chunk count='1' index='10' group='14' type='4'>
            <datum type='4'>20.0</datum>
          </chunk>
          <chunk count='1' index='11' group='14' type='2'>
            <datum type='4'>0.5</datum>
          </chunk>
          <chunk count='1' index='12' group='14' type='3'>
            <datum type='4'>10.0</datum>
          </chunk>
          <chunk count='1' index='13' group='14' type='3'>
            <datum type='4'>10.0</datum>
          </chunk>
          <chunk count='1' index='14' group='14' type='3'>
            <datum type='4'>190.0</datum>
          </chunk>
          <chunk count='1' index='15' group='14' type='3'>
            <datum type='4'>262.0</datum>
          </chunk>
        </section>
      </section>
      <section index='6' group='6'>
        <chunk count='0' index='6' group='6' type='0'/>
        <section index='0' group='15'>
          <chunk count='4' index='0' group='15' type='0'/>
          <chunk count='1' index='1' group='15' type='1'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='2' group='15' type='2'>
            <datum>0</datum>
          </chunk>
          <chunk count='1' index='3' group='15' type='4'>
            <datum>10.0</datum>
          </chunk>
          <chunk count='1' index='4' group='15' type='1'>
            <datum>0</datum>
          </chunk>
        </section>
      </section>
    </section>
    <chunk count='1' index='0' group='16' type='3'>
      <datum>0</datum>
    </chunk>
  </body>
</gsd>'''
TYPEKEY = [None,('b',1),('h',2),('l',4),('d',8),('s',1)]

def debug(what):
    sys.stderr.write(str(what) + "\n")
    return what
 
def metaopen(what):
    if hasattr(what,'read'):
        if hasattr(what,'_bufsize'):
            return what
        else:
            return cStringIO.StringIO(what.read())
    elif os.path.isfile(what):
        return open(what, 'rb')
    else:
        return cStringIO.StringIO(what)

class GSD:
    def __init__(self):
        self.doc = et.fromstring(GSD_MINIMAL)
        self.drawing = self.doc.xpath('/gsd/body/drawing')[0]
    def lexer(self, file):
        #@staticmethod
        f = metaopen(file)
        yield struct.unpack('16s 112B', f.read(128))
        while True:
            b4 = f.read(4)
            if not b4: 
                break
            line, section, type, count = struct.unpack('4B',b4)
            
            if line == 5 and section == 11:
                extra_num, local_count = struct.unpack('2B', f.read(2))
                fmt = "%ss" % local_count
                local_str = struct.unpack(fmt, f.read(local_count))[0]
                data = (extra_num, local_count, local_str)
            elif line > 9 and line < 16 and section == 14:
                data = struct.unpack('d', f.read(8))
            else:
                if not len(TYPEKEY)>type:
                    data = ['ERROR: unknown type %s found' % (type)]
                elif TYPEKEY[type]:
                    fmt = "%s%s" % (count, TYPEKEY[type][0])
                    read_length = TYPEKEY[type][1] * count
                    incoming_data = f.read(read_length)
                    if len(incoming_data) < read_length:
                        data = ['ERROR: unable to read enough data for type. Expected %s but found %s.' % (read_length, len(incoming_data))]
                    else:
                        data = struct.unpack(fmt, incoming_data)
                else:
                    data = []
                    
            yield [line, section, type, count, data]
    def setPageSize(self, width, height):
        # size in mm
        self.setValues(5,8,[int(100*width),int(100*height)])        
    def getValues(self, index, group):
        retval = []
        path = "/gsd/body//chunk[@index='%s'][@group='%s']" % (index, group)
        objects = self.doc.xpath(path)
        if len(objects) == 1:
            node = objects[0]
            type = int(node.get('type'))

            for child in node:
                subtype = type
                if child.has_key('type'):
                    subtype = int(child.get('type'))
                value = child.text
                
                if subtype == 5:
                    pass
                elif subtype == 4:
                    value = float(value)
                else:
                    value = int(value)
                retval.append(value)
        return retval
    def setValues(self, index, group, values):
        path = "/gsd/body//chunk[@index='%s'][@group='%s']" % (index, group)
        objects = self.doc.xpath(path)
        if len(objects) == 1:
            node = objects[0]
            node.set('count', str(len(values)))
            type = int(node.get('type'))
            for child in node:
                node.remove(child)
            for i in values:
                    datum = et.SubElement(node, 'datum')
                    datum.text = str(i)
                    #special case for registration mark floats
                    if index > 9 and group == 14:
                        datum.set('type',str(4))
        else:
            #TODO: raise error
            pass
    def createSection(self, index, group, doc = None):
        if doc is None:
            doc = self.doc
        section = et.Element('section')
        section.set('group',str(group))
        section.set('index',str(index))
        return section
    def createChunk(self, index, group, type, count, data = [], doc = None):
        if doc is None:
            doc = self.doc
        chunk = et.Element('chunk')
        chunk.set('index', str(index))
        chunk.set('group', str(group))
        chunk.set('type',str(type))
        chunk.set('count',str(count))
        # special case for a string object that contains a few extra bytes of data
        if index == 5 and group == 11:
            i = data[0]
            datum = et.Element('datum')
            datum.text = str(i)
            chunk.append(datum)
            
            i = data[1]
            datum = et.Element('datum')
            datum.text = str(i)
            chunk.append(datum)
            
            i = data[2]
            datum = et.Element('datum')
            datum.set('type',str(5))
            datum.text = str(i)
            chunk.append(datum)
        else:
            for i in data:
                datum = et.Element('datum')
                datum.text = repr(i)
                #special case for registration mark floats
                if index > 9 and index < 16 and group == 14:
                    datum.set('type',str(4))
                chunk.append(datum)
        return chunk
    def appendPolyline(self, points):
        # increment the drawing object count
        count = self.getValues(1,3)[0]
        self.setValues(1,3,[count+1])
        
        pl = self.createSection(0,4)
        pl.append(self.createChunk(0,4,0,24))
        #drawing object type 1 = polyline
        pl.append(self.createChunk(1,4,3,1,[1]))
        pl.append(self.createChunk(2,4,3,1,[0]))
        pl.append(self.createChunk(3,4,3,1,[0]))
        pl.append(self.createChunk(4,4,3,1,[0]))
        pl.append(self.createChunk(32,5,1,1,[0]))
        pl.append(self.createChunk(6,4,1,1,[0]))
        pl.append(self.createChunk(7,4,1,1,[0]))
        pl.append(self.createChunk(0,5,3,1,[0]))
        pl.append(self.createChunk(1,5,3,1,[1]))
        pl.append(self.createChunk(16,5,3,1,[16777215]))
        pl.append(self.createChunk(18,5,3,1,[0]))
        pl.append(self.createChunk(19,5,3,1,[0]))
        pl.append(self.createChunk(20,5,3,1,[0]))
        pl.append(self.createChunk(21,5,3,1,[16777215]))
        pl.append(self.createChunk(22,5,3,1,[50]))
        pl.append(self.createChunk(23,5,3,1,[50]))
        pl.append(self.createChunk(24,5,4,1,[0.0]))
        
        #calculate bbox and prepare pointNodes
        minx = points[0][0]
        maxx = points[0][0]
        miny = points[0][1]
        maxy = points[0][1]
        pnodes = []
        for p in points:
            if p[0] < minx:
                minx = p[0]
            if p[0] > maxx:
                maxx = p[0]
            if p[1] < miny:
                miny = p[1]
            if p[1] > maxy:
                maxy = p[1]
            pnodes.append(self.createChunk(12,4,4,3,[p[0],p[1],0.0]))
        
        # define bbox
        pl.append(self.createChunk(64,4,4,2,[minx,miny]))
        pl.append(self.createChunk(65,4,4,2,[maxx,miny]))
        pl.append(self.createChunk(66,4,4,2,[maxx,maxy]))
        pl.append(self.createChunk(67,4,4,2,[minx,maxy]))
        # rotation
        pl.append(self.createChunk(68,4,4,1,[0.0]))
        # group (-1 = no group)
        pl.append(self.createChunk(33,5,3,1,[-1]))
        sec = self.createSection(8,4)
        pl.append(sec)
        sec.append(self.createChunk(8,4,3,1,[2]))
        # open polyline
        sec.append(self.createChunk(22,4,3,1,[0]))
        psec = self.createSection(11,4)
        sec.append(psec)
        psec.append(self.createChunk(11,4,3,1,[len(pnodes)]))
        for n in pnodes:
            psec.append(n)
        self.drawing.append(pl)
    def fromXML(self, file):
        f = metaopen(file)
        self.doc = et.parse(f)
        f.close()
        self.drawing = self.doc.xpath('/gsd/body/drawing')[0]
    def fromGSD(self, file):
        lexer = self.lexer(file)
        doc = et.fromstring(GSD_DOCROOT)
        
        # read the header and the unidentified padding bytes
        head = lexer.next()
        magic = head[0]
        pad = head[1:]
        pad = ','.join([str(x) for x in pad])
        header = et.Element('header')
        m = et.Element('magic')
        p = et.Element('unknown')
        m.text = magic
        p.text = pad
        header.append(m)
        header.append(p)
        doc.append(header)
        
        body = et.Element('body')
        doc.append(body)
        
        # stack of xml dom nodes to append to
        xstack = [body]
        # stack of counts before we pop back to the previous level
        cstack = [None]
        while True:
            try:
                index, group, type, count, data = lexer.next()
            except StopIteration:
                break
            
            # travel back up the stack when counts have been satisfied
            while cstack[-1] == 0:
                xstack.pop()
                cstack.pop()
            
            newsection = False
            if type == 0:
                if cstack[-1]:
                    cstack[-1] = cstack[-1] - 1
                # count of 0 happens often in section 6
                if count == 0:
                    cstack.append(1)
                else:
                    cstack.append(count)
                newsection = True
            # some lines that don't have type 0 also indicate sections
            elif (index,group) == (8,4) or (index,group) == (11,4) or (index,group) == (2,7):
                if cstack[-1]:
                    cstack[-1] = cstack[-1] - 1
                cstack.append(data[0])
                newsection = True
            else:
                if cstack[-1]:
                    cstack[-1] = cstack[-1] - 1
            
            if newsection:
                section = self.createSection(index, group, doc)
                xstack[-1].append(section)
                xstack.append(section)
                
            chunk = self.createChunk(index, group, type, count, data, doc)
            xstack[-1].append(chunk)
        
        # do some processing I wasn't smart enough to know how to do in the above loop
        
        # make all 84s that are direct children of body 
        # a child of their immediate previous sibling
        path = "/gsd/body/section[@index='8'][@group='4']"
        for node in doc.xpath(path):
            #fixme
            node.getprevious().append(node)
        
        # move drawing objects into their own section
        path = "/gsd/body/section[@index='0'][@group='4']"
        objects = doc.xpath(path)
        parent = et.Element('drawing')
        drawing_location = 0
        if len(objects) > 0:
            for x in range(len(body)):
                if body[x] == objects[0]:
                    drawing_location = x

        body.insert(drawing_location, parent)

        for node in objects:
            parent.append(node)
        
        self.doc = doc
        self.drawing = parent
    def outputXML(self):
        sys.stdout.write(et.tostring(self.doc, pretty_print=True))
    def outputXMLminimal(self):
        self.doc.write(sys.stdout)
    def outputGSD(self):
        gsd = ''
        
        path = '/gsd/header/magic/text()'
        header = self.doc.xpath(path)[0]
        gsd = gsd + struct.pack('%ss' % len(header), str(header))
        
        path = '/gsd/header/unknown/text()'
        unknown = self.doc.xpath(path)[0]
        unknown = [int(x) for x in unknown.split(',')]
        for x in unknown:
            gsd = gsd + struct.pack('1B', x)
        
        path = '//chunk'
        for node in self.doc.xpath(path):
            index = int(node.get('index'))
            group = int(node.get('group'))
            type = int(node.get('type'))
            count = int(node.get('count'))
            gsd = gsd + struct.pack('4B',index,group,type,count)
            if len(node):
                for child in node:
                    subcount = 1
                    subtype = type
                    if child.get('type'):
                        subtype = int(child.get('type'))
                    
                    if child.text:
                        value = child.text
                    else:
                        value = ''
                    
                    if subtype == 5:
                        subcount = len(value)
                        value = str(value)
                    elif subtype == 4:
                        value = float(value)
                    else:
                        value = int(value)
                    
                    fmt = "%s%s" % (subcount,TYPEKEY[subtype][0])
                    gsd = gsd + struct.pack(fmt, value)

        sys.stdout.write(gsd)
    def outputTXT(self):
        txt = ''
        
        path = '/gsd/header/magic/text()'
        header = self.doc.xpath(path)[0]
        txt = "%s%s\n" % (txt, header)
        
        path = '/gsd/header/unknown/text()'
        unknown = self.doc.xpath(path)[0]
        txt = "%s%s\n" % (txt, unknown)
        
        path = '//chunk'
        for node in self.doc.xpath(path):
            index = "%02X" % int(node.get('index'))
            group = "%02X" % int(node.get('group'))
            type = "%02X" % int(node.get('type'))
            count = int(node.get('count'))
            spacer = ''
            if type == '00':
                spacer = "\n"
            txt = "%s%s%s %s %s %s (" % (txt, spacer, index, group, type, count)
            if len(node):
                for child in node:
                    value = child.text
                    txt = "%s %s" % (txt, value)
            description = GSD_DICT.get(group,{}).get(index,'')
            if description:
                description = '-- %s' % description
            txt = "%s )\t%s\n" % (txt, description)
        sys.stdout.write(txt)
        
if __name__ == '__main__':
    filename = sys.argv[-1]
    g = GSD()
    g.fromGSD(filename)
    #g.appendPolyline(((0,0),(0,50),(50,50),(50,100)))
    #g.setValues(5,8,[3,4])
    #print g.getValues(5,8)
    g.outputTXT()
